import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { PointerEventTypes, PointerInfo } from '@babylonjs/core/Events/pointerEvents';
import { Matrix, Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { CreateHandleTo, IHandle, RemoveHandle } from '../data/data-handle';
import { Stage } from '../base/stage';
import { XRStage } from '../base/xr-stage';
import { IVRContainer, VRContainer } from '../xr/vr-container';
import { IARContainer, ARContainer } from '../xr/ar-container';
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { Mesh } from '@babylonjs/core/Meshes/mesh';
import { Helper } from 'babylon-lib/tool/helper';


/**
 * 触点容器
 */
class PointerContainer {

    private static _entity: TransformNode;
    private static _container: TransformNode;
    private static _stage: Stage;
    private static _xr: IVRContainer | IARContainer | null;
    private static _pointer = new Vector3();
    private static _wld = new Vector3();
    private static _isVisible = true;
    private static _pointerDownHandles: IHandle<PointerInfo>[] = [];
    private static _pointerMoveHandles: IHandle<PointerInfo>[] = [];
    private static _pointerUpHandles: IHandle<PointerInfo>[] = [];
    private static _tm0 = new Matrix();
    private static _xrLoadedHandle: IHandle<AbstractMesh>;
    private static _xrInHandle: IHandle<VRContainer | ARContainer>;
    private static _xrNotInHandle: IHandle<VRContainer | ARContainer>;

    private static _rightHand: TransformNode;
    private static _leftHand: TransformNode;

    /**
     * 容器深度位置（视向坐标系z向）
     */
    public static depth = 0.7;
    /**
     * 控制器模型不透明度
     */
    public static controllerVisibility = 0.2;

    public static enabled = true;
    /**
     * 容器节点位置偏移（为触点时）
     */
    public static pointerOffset = new Vector3(0, 0, 0);
    /**
     * 容器节点位置偏移（手柄时）
     */
    public static gripOffset = new Vector3(0, 0, 0);

    /**
     * 容器节点
     */
    public static get container(): TransformNode {
        return this._container;
    }

    /**
     * 容器内模型是否可见
     */
    public static set isVisible(isVisible: boolean) {
        this._isVisible = isVisible;
        const meshes = this._entity.getChildMeshes(false);
        meshes.forEach(m => m.isVisible = isVisible);
    }

    public static get isVisible(): boolean {
        return this._isVisible;
    }

    /**
     * 初始化函数
     * @param stage 
     */
    public static initialize(stage: Stage): void {
        const scope = PointerContainer;
        if (!this._entity) {
            this._entity = new TransformNode('pointer-container-entity', stage.mainScene);
            this._container = new TransformNode('pointer-container-container', stage.mainScene);
        } else {
            this._stage.mainScene.removeTransformNode(this._entity);
            this._stage.mainScene.removeTransformNode(this._container);
            this._stage.mainScene.onPointerObservable.removeCallback(PointerContainer.pointerHandle);
            stage.mainScene.addTransformNode(this._entity);
            stage.mainScene.addTransformNode(this._container);
            if (this._xr) {
                if (this._xrLoadedHandle) this._xr.xrInput.removeLoadedHandle(this._xrLoadedHandle);
                if (this._xrInHandle) this._xr.removeInHandle(this._xrInHandle);
                if (this._xrNotInHandle) this._xr.removeNotInHandle(this._xrNotInHandle);
            }
        }
        this._container.parent = this._entity;
        this.container.position.set(0, 0, 0);
        this.container.rotationQuaternion = Quaternion.Identity();
        this._entity.rotationQuaternion = Quaternion.Identity();
        stage.mainScene.onPointerObservable.add(PointerContainer.pointerHandle);

        this._xr = (stage as XRStage).vr || (stage as XRStage).ar;
        if (Helper.isVrSupported || Helper.isArSupported) {
            this._xrLoadedHandle = this._xr!.xrInput.onLoaded(PointerContainer.xrLoadedHandle);
            this._xrInHandle = this._xr!.onIn(() => {
                PointerContainer._container.parent = PointerContainer._leftHand || PointerContainer._rightHand;
                PointerContainer._container.position.copyFrom(scope.gripOffset);
                PointerContainer._container.rotationQuaternion!.set(0, 0, 0, 1);
            }) as any;
            this._xrNotInHandle = this._xr!.onNotIn(() => {
                PointerContainer._container.parent = PointerContainer._entity;
                PointerContainer._container.position.copyFrom(scope.pointerOffset);
                PointerContainer._container.rotationQuaternion!.set(0, 0, 0, 1);
            }) as any;
        }

        this._stage = stage;
    }

    /**
     * 触点按下时
     * @param func 
     * @returns 
     */
    public static onPointerDown(func: { (eventData: PointerInfo): void }): IHandle<PointerInfo> {
        return CreateHandleTo(func, this._pointerDownHandles);
    }

    public static removePointerDownHandle(handle: number | IHandle<PointerInfo>): void {
        RemoveHandle(handle, this._pointerDownHandles);
    }

    /**
     * 触点移动时
     * @param func 
     * @returns 
     */
    public static onPointerMove(func: { (eventData: PointerInfo): void }): IHandle<PointerInfo> {
        return CreateHandleTo(func, this._pointerMoveHandles);
    }

    public static removePointerMoveHandle(handle: number | IHandle<PointerInfo>): void {
        RemoveHandle(handle, this._pointerMoveHandles);
    }

    /**
     * 触点抬起时
     * @param func 
     * @returns 
     */
    public static onPointerUp(func: { (eventData: PointerInfo): void }): IHandle<PointerInfo> {
        return CreateHandleTo(func, this._pointerUpHandles);
    }

    public static removePointerUpHandle(handle: number | IHandle<PointerInfo>): void {
        RemoveHandle(handle, this._pointerUpHandles);
    }

    private static xrLoadedHandle(model: AbstractMesh): void {
        const _this = PointerContainer;
        model.getChildren((n) => { return n instanceof Mesh }, false).forEach(c => {
            (c as Mesh).visibility = PointerContainer.controllerVisibility;
        });
        const regR = new RegExp('right', 'i');
        const regL = new RegExp('left', 'i');
        const name = model.name;
        const gp0 = _this._xr!.input.controllers[0] ? _this._xr!.input.controllers[0].grip : null;
        const gp1 = _this._xr!.input.controllers[1] ? _this._xr!.input.controllers[1].grip : null;
        if (regR.test(name)) {
            if (gp0 && regR.test(gp0.id)) _this._rightHand = gp0;
            if (gp1 && regR.test(gp1.id)) _this._rightHand = gp1;
        } else if (regL.test(name)) {
            if (gp0 && regL.test(gp0.id)) _this._leftHand = gp0;
            if (gp1 && regL.test(gp1.id)) _this._leftHand = gp1;
        } else {
            console.warn('未获取到xr控制器模型!');
        }
    }

    private static pointerHandle(eventData: PointerInfo): void {
        const _this = PointerContainer;
        if (_this.enabled) {
            const scene = _this._stage.mainScene;
            const wld = _this._wld;
            const pointer = _this._pointer;
            const entity = _this._entity;
            pointer.set(eventData.event.clientX, eventData.event.clientY, 0);
            const camera = scene.activeCamera;
            if (camera) {
                if ((!entity.parent) || (entity.parent.id !== camera.id)) {
                    entity.parent = camera;
                }
                if (entity.parent && entity.parent.id === camera.id) { }
                const engine = scene.getEngine();
                const w = engine.getRenderWidth();
                const h = engine.getRenderHeight();
                const r = engine.getHardwareScalingLevel();
                Vector3.UnprojectToRef(pointer, w * r, h * r, Matrix.IdentityReadOnly, camera.getViewMatrix(), camera.getProjectionMatrix(), wld);
                const inv = _this._tm0.copyFrom(camera.getWorldMatrix());
                Vector3.TransformCoordinatesToRef(wld, inv.invert(), wld);
                wld.normalize().scaleInPlace(_this.depth);
                entity.position.copyFrom(wld);
            }
        }
        switch (eventData.type) {
            case PointerEventTypes.POINTERDOWN:
                if (eventData.pickInfo) {
                    if (eventData.pickInfo.gripTransform) {
                        const name = eventData.pickInfo.gripTransform.name;
                        const reg = new RegExp('right', 'i');
                        if (reg.test(name)) {
                            _this._container.parent = _this._rightHand;
                            _this._container.position.copyFrom(_this.gripOffset);
                        } else {
                            _this._container.parent = _this._leftHand;
                            _this._container.position.copyFrom(_this.gripOffset);
                        }
                    } else {
                        _this._container.parent = _this._entity;
                        _this._container.position.copyFrom(_this.pointerOffset);
                    }
                    _this._container.rotationQuaternion!.set(0, 0, 0, 1);
                }
                if (_this.enabled) _this._pointerDownHandles.forEach(handle => handle.callback(eventData));
                break;
            case PointerEventTypes.POINTERMOVE:
                if (_this.enabled) _this._pointerMoveHandles.forEach(handle => handle.callback(eventData));
                break;
            case PointerEventTypes.POINTERUP:
                if (_this.enabled) _this._pointerUpHandles.forEach(handle => handle.callback(eventData));
                break;
        }
    }

}


export { PointerContainer };